在點對點連接前需要先收集 ICE 找到最適合的連線方式再交換 SPD 資訊,上篇先完成了 SDP 交換,接著這篇會完成 ICE 候選人的收集。
onicecandidate
:RTCIceCandidate
方法 toJSON()
將調用它的 轉換為 JSON,addDoc 加入onicecandidateerror
:處理收集到的每個候選人若有錯誤時觸發async function collectIceCandidates(
roomRef,
peerConnection,
localName,
remoteName
) {
// callerCandidates 存儲本地 ICE 候選者
const candidatesCollection = collection(roomRef, localName);
try {
// 當收集到一個本地 ICE 候選者時,將觸發這個事件
peerConnection.onicecandidate = async (event) => {
// 如果事件中存在候選者,則將其轉換為 JSON 對象並添加到 candidatesCollection 中
if (event.candidate) {
console.log(event);
await addDoc(candidatesCollection, event.candidate.toJSON());
}
};
// 收集到一個本地 ICE 候選者時錯誤則觸發
peerConnection.onicecandidateerror = (error) => {
console.log("error", error);
};
// 監聽 calleeCandidates 裡的每個 doc
const remoteCandidatesCollection = collection(roomRef, remoteName);
onSnapshot(remoteCandidatesCollection, (snapshot) => {
snapshot.docChanges().forEach(async (change) => {
if (change.type === "added") {
// 對於每個新增的遠程 ICE 候選者,將其轉換為 RTCIceCandidate
const data = change.doc.data();
await peerConnection.addIceCandidate(new RTCIceCandidate(data));
}
});
});
} catch (error) {
console.error(error);
}
async function createRoom() {
// 創建一個新的 RTCPeerConnection
if (!localStream.current) {
alert('請先開啟視訊及麥克風');
return;
}
const pc = new RTCPeerConnection(configuration);
// 創建房間
const roomRef = await addDoc(collection(db, "rooms"), {});
const roomId = roomRef.id;
window.alert(roomId);
collectIceCandidates(roomRef, pc, "calleeCandidates", "callerCandidates")
// 略...
}
async function joinRoom(roomId) {
if (!localStream.current) {
alert('請先開啟視訊及麥克風');
return;
}
// 取的輸入 id 的 room
const roomRef = doc(db, "rooms", roomId);
const roomSnapshot = await getDoc(roomRef);
// 創建一個新的 RTCPeerConnection
if (roomSnapshot.exists()) {
const pc = new RTCPeerConnection(configuration);
collectIceCandidates(roomRef, pc, "callerCandidates", "calleeCandidates")
// 略...
}
}
這篇完成了 ICE 候選人的新增與交換,從 Firebase db 可以看到 await addDoc(candidatesCollection, event.candidate.toJSON())
,下篇就把我們交換後的資訊將遠端stream
render 到畫面上。